﻿// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace SnapObjects.Data.Odbc
{
    internal class OdbcAseSqlColumnSchemaProvider : SqlColumnSchemaProvider
    {

        private const string SQL_COLUMN_INFO = @"
SELECT '{0}',
       '{1}',
       name
  FROM syscolumns
 WHERE id = object_id('{2}') 
   AND computedcol > 0";

        public OdbcAseSqlColumnSchemaProvider(DataContext dataContext) : base(dataContext)
        {
        }

        public override IReadOnlyList<DbColumnInfo> GetColumnSchema(
            string sqlText, IEnumerable<IAdoDbParameter> parameters)
        {
            IReadOnlyList<DbColumnInfo> dbColumnInfo;

            try
            {
                dbColumnInfo = base.GetColumnSchema(sqlText, parameters);

                this.ExtraHandlePrimaryKey(dbColumnInfo);

                this.ExtraHandleExpression(dbColumnInfo);
            }
            catch
            {
                _dataContext.CurrentConnection.Open();

                var adoDbProvider = _dataContext.AdoDbProviderFactory.GetAdoDbProvider();

                IAdoDbDataVisitor dataVisitor = null;

                try
                {
                    dataVisitor = adoDbProvider.ExecuteSqlQuery(
                        sqlText, parameters ?? new List<IAdoDbParameter>(), CommandBehavior.Default);

                    dbColumnInfo = this.CreateDbColumnInfo(dataVisitor.ReadData());
                }
                finally
                {
                    dataVisitor?.Dispose();
                    _dataContext.CurrentConnection.Close();
                }
            }

            return dbColumnInfo;
        }

        public async override Task<IReadOnlyList<DbColumnInfo>> GetColumnSchemaAsync(
            string sqlText, IEnumerable<IAdoDbParameter> parameters, CancellationToken cancellationToken)
        {
            IReadOnlyList<DbColumnInfo> dbColumnInfo;

            try
            {
                dbColumnInfo = await base.GetColumnSchemaAsync(sqlText, parameters, cancellationToken);

                await this.ExtraHandlePrimaryKeyAsync(dbColumnInfo, cancellationToken);

                await this.ExtraHandleExpressionAsync(dbColumnInfo, cancellationToken);
            }
            catch
            {
                await _dataContext.CurrentConnection.OpenAsync(cancellationToken);

                var adoDbProvider = _dataContext.AdoDbProviderFactory.GetAdoDbProvider();

                IAdoDbDataVisitor dataVisitor = null;

                try
                {
                    dataVisitor = await adoDbProvider.ExecuteSqlQueryAsync(
                        sqlText, parameters ?? new List<IAdoDbParameter>(), CommandBehavior.Default, cancellationToken);

                    dbColumnInfo = this.CreateDbColumnInfo(dataVisitor.ReadData());
                }
                finally
                {
                    if (dataVisitor != null)
                    {
                        dataVisitor?.Dispose();
                    }

                    await _dataContext.CurrentConnection.CloseAsync();
                }
            }

            return dbColumnInfo;
        }

        protected override bool IsIdentity(DataRow row, IDataReader reader)
        {
            var column = row.Table.Columns["isautoincrement"];

            var value = row.ItemArray[column.Ordinal];

            var identity = this.GetSchemaItemValue<bool>(value);

            if (!identity)
            {
                var col = row.Table.Columns["isreadonly"];

                var v = row.ItemArray[col.Ordinal];

                var readOnly = this.GetSchemaItemValue<bool>(v);

                if (readOnly)
                {
                    var schema = this.ColumnSchemaName(row, reader);
                    var table = this.ColumnTableName(row, reader);

                    if (!String.IsNullOrWhiteSpace(schema) && !String.IsNullOrWhiteSpace(table))
                    {
                        identity = true;
                    }
                }
            }

            return identity;
        }

        protected override bool IsExpression(DataRow row, IDataReader reader)
        {
            if (!this.IsIdentity(row, reader))
            {
                var column = row.Table.Columns["isreadonly"];

                var value = row.ItemArray[column.Ordinal];

                return this.GetSchemaItemValue<bool>(value);
            }

            return false;
        }

        protected override bool IsReadOnly(DataRow row, IDataReader reader)
        {
            return this.IsExpression(row, reader);
        }

        protected override string ColumnDataTypeName(DataRow row, IDataReader reader)
        {
            var ordinal = this.ColumnOrdinal(row, reader);

            if (reader != null)
            {
                return reader.GetDataTypeName(ordinal);
            }

            return null;
        }

        protected override bool IsHidden(DataRow row, IDataReader reader)
        {
            return false;
        }

        private void ExtraHandlePrimaryKey(IReadOnlyList<DbColumnInfo> columns)
        {
            if (!columns.Any(m => m.IsPrimaryKey))
            {
                var column = columns.FirstOrDefault(m =>
                !String.IsNullOrEmpty(m.TableSchema) &&
                !String.IsNullOrEmpty(m.Table) &&
                !String.IsNullOrEmpty(m.ColumnName));

                if (column != null)
                {
                    var keyColumns = _dataContext.SqlExecutor.Select<DynamicModel>(
                        "sp_pkeys @table_name, @owner", column.Table, column.TableSchema);

                    this.SetPrimaryKey(keyColumns, columns);
                }
            }
        }

        private void SetPrimaryKey(IList<DynamicModel> keyColumns, IReadOnlyList<DbColumnInfo> columns)
        {
            foreach (var keyColumn in keyColumns)
            {
                var tempCol = columns.FirstOrDefault(m =>
                keyColumn.GetValue<string>(1).Equals(m.TableSchema) &&
                keyColumn.GetValue<string>(2).Equals(m.Table) &&
                keyColumn.GetValue<string>(3).Equals(m.ColumnName));

                if (tempCol != null)
                {
                    tempCol.IsPrimaryKey = true;
                }
            }
        }

        private async Task ExtraHandlePrimaryKeyAsync(IReadOnlyList<DbColumnInfo> columns, CancellationToken cancellationToken)
        {
            if (!columns.Any(m => m.IsPrimaryKey))
            {
                var column = columns.FirstOrDefault(m =>
                !String.IsNullOrEmpty(m.TableSchema) &&
                !String.IsNullOrEmpty(m.Table) &&
                !String.IsNullOrEmpty(m.ColumnName));

                if (column != null)
                {
                    var keyColumns = await _dataContext.SqlExecutor.SelectAsync<DynamicModel>(
                        "sp_pkeys @table_name, @owner", column.Table, column.TableSchema, cancellationToken);

                    this.SetPrimaryKey(keyColumns, columns);
                }
            }
        }

        private void ExtraHandleExpression(IReadOnlyList<DbColumnInfo> columns)
        {
            var column = columns.FirstOrDefault(m =>
                !String.IsNullOrEmpty(m.TableSchema) &&
                !String.IsNullOrEmpty(m.Table) &&
                !String.IsNullOrEmpty(m.ColumnName));

            if (column != null)
            {
                try
                {
                    var computes = _dataContext.SqlExecutor.Select<DynamicModel>(
                        String.Format(SQL_COLUMN_INFO, column.TableSchema, column.Table, $"{column.TableSchema}.{column.Table}"));

                    this.SetExpression(computes, columns);
                }
                catch
                {
                    //Handles the incompatibility with the calculated columns from a database of lower version. 
                }
            }
        }

        private async Task ExtraHandleExpressionAsync(
            IReadOnlyList<DbColumnInfo> columns, CancellationToken cancellationToken)
        {
            var column = columns.FirstOrDefault(m =>
                !String.IsNullOrEmpty(m.TableSchema) &&
                !String.IsNullOrEmpty(m.Table) &&
                !String.IsNullOrEmpty(m.ColumnName));

            if (column != null)
            {
                var computes = await _dataContext.SqlExecutor.SelectAsync<DynamicModel>(
                    String.Format(SQL_COLUMN_INFO, column.TableSchema, column.Table,
                    $"{column.TableSchema}.{column.Table}"),
                    cancellationToken);

                this.SetExpression(computes, columns);
            }
        }

        private void SetExpression(IList<DynamicModel> computes, IReadOnlyList<DbColumnInfo> columns)
        {
            foreach (var compute in computes)
            {
                var tempCol = columns.FirstOrDefault(m =>
                compute.GetValue<string>(0).Equals(m.TableSchema) &&
                compute.GetValue<string>(1).Equals(m.Table) &&
                compute.GetValue<string>(2).Equals(m.ColumnName));

                if (tempCol != null)
                {
                    tempCol.IsExpression = true;
                    tempCol.IsIdentity = false;
                }
            }
        }
    }
}
